/**
 * 
 */
package eu.jakubiak.translation.holders;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Embeddable;

/**
 * Translation are stored in the same table as a field for translation
 * as a binary data.
 * The advantage of this is time for read
 * The disadvantage is their are unreadeble from SQL level.  
 * 
 * @author Antek
 *
 * @param <V>
 */
@Embeddable
public class ByteaHolderTranslation<V> implements Serializable, TranslationHolder<V> {

	public class Entry implements java.util.Map.Entry<Locale, V>{
		
		private ByteaHolderTranslation<V> holder;

		private Locale key;
		private V value;
		public Entry(ByteaHolderTranslation<V> holder, Locale key, V value) {
			super();
			this.holder = holder;
			this.key = key;
			this.value = value;
		}

		public Locale getKey() {
			return key;
		}

		public V getValue() {
			return value;
		}

		public V setValue(V value) {
			this.value = value;
			holder.put(key, value);
			return value;
		}
		
		
		
	}
	

	/**
	 * 
	 */
	private static final long serialVersionUID = -565915926987396380L;

	@Column
	private byte[] data;

	public void clear() {
		data = null;
	}

	public boolean containsKey(Object key) {
		return this.get(key) != null;
	}

	public boolean containsValue(Object value) {
		ObjectInputStream ois = getObjectInputStream();
		try {
			while(true) {
				ois.readObject();
				if (value.equals(ois.readObject())) { 
					return true;
				}
			}
		} catch(EOFException e) {
			return false;
		} catch(Exception e) {
			throw new RuntimeException(e);
		}
	}

	@SuppressWarnings("unchecked")
	public Set<java.util.Map.Entry<Locale, V>> entrySet() {
		Set<java.util.Map.Entry<Locale, V>> s = new HashSet<java.util.Map.Entry<Locale,V>>();
		ObjectInputStream ois = getObjectInputStream();
		try {
			while(true) {
				Locale locale = (Locale)ois.readObject();
				V value = (V)ois.readObject();
				s.add(new Entry(this, locale, value));
			}
		} catch(EOFException e) {
			return s;
		} catch(Exception e) {
			throw new RuntimeException(e);
		}
	}

	@SuppressWarnings("unchecked")
	public V get(Object key) {
		ObjectInputStream ois = getObjectInputStream();
		try {
			while(true) {
				if (key.equals(ois.readObject())) { 
					return (V)ois.readObject();
				}
				ois.readObject();
			}
		} catch(EOFException e) {
			return null;
		} catch(Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * @return the data
	 */
	protected byte[] getData() {
		return data;
	}
	
	
	
	

	private ObjectInputStream getObjectInputStream() {
		try {
			return new ObjectInputStream(new ByteArrayInputStream(data));
		} catch(IOException e) {
			throw new RuntimeException(e);
		}
	}

	public boolean isEmpty() {
		return null == data || 0 == data.length;
	}

	public Set<Locale> keySet() {
		Set<Locale> ss = new HashSet<Locale>();
		ObjectInputStream ois = getObjectInputStream();
		try {
			while(true) {
				ss.add((Locale)ois.readObject());
				ois.readObject();
			}
		} catch(EOFException e) {
			return ss;
		} catch(Exception e) {
			throw new RuntimeException(e);
		}
	}

	@SuppressWarnings("unchecked")
	public V put(Locale key, V value) {
		V old = null;
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		try {
			ObjectOutputStream oos = new ObjectOutputStream(baos);
			oos.writeObject(key);
			oos.writeObject(value);
			if(!isEmpty()) {
				try {
					ObjectInputStream ois = getObjectInputStream();
					while(true) {
						Locale locale = (Locale)ois.readObject();
						if(!key.equals(locale)) {
							oos.writeObject(locale);
							oos.writeObject(ois.readObject());
						} else {
							old = (V)ois.readObject();
						}
					}
				} catch(EOFException e) {
					// TODO logger
				} catch(ClassNotFoundException e) {
					throw new RuntimeException(e);
				}
			}
		} catch (IOException e) {
			// TODO logger
		}
		data = baos.toByteArray();
		return old;
	}

	

	public void putAll(Map<? extends Locale, ? extends V> m) {
		for (Iterator<? extends Map.Entry<? extends Locale, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {
            Map.Entry<? extends Locale, ? extends V> e = i.next();
            put(e.getKey(), e.getValue());
        }		
	}

	@SuppressWarnings("unchecked")
	public V remove(Object key) {
		if(isEmpty()) {
			return null;
		}
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		V old = null;
		try {
			ObjectOutputStream oos = new ObjectOutputStream(baos);
			ObjectInputStream ois = getObjectInputStream();
			while(true) {
				Locale locale = (Locale)ois.readObject();
				if(key.equals(locale)) {
					old = (V)ois.readObject();
				} else {
					oos.writeObject(locale);
					oos.writeObject(ois.readObject());
				}
			}
		} catch(EOFException e) {
			data = baos.toByteArray();
			return old;
		} catch(Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * @param data the data to set
	 */
	protected void setData(byte[] data) {
		this.data = data;
	}
	
	public int size() {
		if(isEmpty()) {
			return 0;
		}
		ObjectInputStream ois = getObjectInputStream();
		int s = 0;
		try {
			while(true) {
				ois.readObject();
				ois.readObject();
				s++;
			}
		} catch(EOFException e) {
			return s;
		} catch(Exception e) {
			throw new RuntimeException(e);
		}
	}

	@SuppressWarnings("unchecked")
	public Collection<V> values() {
		Collection<V> vs = new LinkedList<V>();
		ObjectInputStream ois = getObjectInputStream();
		try {
			while(true) {
				ois.readObject();
				vs.add((V)ois.readObject());
			}
		} catch(EOFException e) {
			return vs;
		} catch(Exception e) {
			throw new RuntimeException(e);
		}
	}

}
